module Lockdown
  module Controller
    def self.included(base)
      base.send :include, Lockdown::Controller::InstanceMethods

      base.send :helper_method, :authorized?

      base.before_filter do |controller|
        controller.set_current_user
        controller.configure_lock_down
        controller.check_request_authorization
      end
      
      base.filter_parameter_logging :password, :password_confirmation
      
      base.rescue_from SecurityError,
        :with => proc{|e| access_denied(e)}
    end
    
    module InstanceMethods
      def configure_lock_down
        check_session_expiry
        store_location
      end
      
      def set_current_user
        login_from_basic_auth? unless logged_in?
				if logged_in?
					Thread.current[:profile_id] = current_profile_id
					Thread.current[:client_id] = current_client_id if respond_to? :current_client_id
				end
      end
      
      def check_request_authorization
        unless authorized?(path_from_hash(params))
          raise SecurityError.new("Authorization failed for #{params[:controller] + "/" + params[:action]}")
        end
      end
      
      def authorized?(options)
        return true if current_user_is_admin?
        
        url_parts = URI::split url_for(options)
        
        # Passing in different domain
        return true if remote_url?(url_parts[2])

				path = url_parts[5]
        # See if path is known
        return true if path_allowed?(path)
        
        if options.is_a?(String)
          # Test for a named routed
          begin
            hsh = ActionController::Routing::Routes.recognize_path(options)
            return true if path_allowed?(path_from_hash(hsh)) unless hsh.nil?
          rescue Exception => e
            # continue on
          end
        end
          
        # Test for a route without trailing index
        # e.g. /users  which equates to /users/index
        return true if path_allowed?(path + "/index")
        
        # Test to see if using a get method (show)
				path += "/show" if path.split("/").last.to_i > 0
        return true if path_allowed?(path)

        return false
      end
      
      
      def access_denied(e)
				reset_session
        respond_to do |accepts|
          accepts.html do
            store_location
            redirect_to login_path
          end
          accepts.xml do
            headers["Status"] = "Unauthorized"
            headers["WWW-Authenticate"] = %(Basic realm="Web Password")
            render :text => e.message, :status => "401 Unauthorized"
          end
        end
        false
      end
    
      def redirect_back_or_default(default)
        session[:prevpage] ? redirect_to(session[:prevpage]) : redirect_to(default)
      end
  
      private
        def path_allowed?(url)
          req = Lockdown.format_controller_action(url)
          session[:access_rights] ||= Lockdown::UserGroups[:public_access]
          session[:access_rights].each do |ar|
            # The regex comparison is to allow for relative paths
            # e.g. if you are running your app in a subdir you'll want this
            #
            # You can modify to a simple equality test if you are running
            # the 'conventional' setup out of root /
            #
            #   return true if req == ar
            #
            return true if req =~ /#{ar}$/
          end
          false
        end
        
        def remote_url?(domain = nil)
          return false if domain.nil? || domain.strip.length == 0
          request.host.downcase != domain.downcase
        end
        
        def path_from_hash(params)
          params[:controller].to_s + "/" + params[:action].to_s
        end
        
        def check_session_expiry
          if session[:expiry_time] && session[:expiry_time] < Time.now
            reset_session
          end
          session[:expiry_time] = Time.now + Lockdown::SESSION_TIMEOUT
        end
              
        def store_location
          #session[:return_to] = request.request_uri
           p = request.request_uri
           if request.method == :get && !(session[:thispage] == p)
             session[:prevpage] = session[:thispage] || ''
             session[:thispage] = p 
           end
        end
      
        # Called from current_user.  Now, attempt to login by
        # basic authentication information.
        def login_from_basic_auth?
          username, passwd = get_auth_data
          if username && passwd
            set_session_user User.authenticate(username, passwd)
          end
        end
    
        @@http_auth_headers = %w(X-HTTP_AUTHORIZATION HTTP_AUTHORIZATION Authorization)
        # gets BASIC auth info
        def get_auth_data
          auth_key  = @@http_auth_headers.detect { |h| request.env.has_key?(h) }
          auth_data = request.env[auth_key].to_s.split unless auth_key.blank?
          return auth_data && auth_data[0] == 'Basic' ? Base64.decode64(auth_data[1]).split(':')[0..1] : [nil, nil] 
        end
    end # end InstanceMethods
  end # Controller
end # Lockdown

ActionController::Base.send :include, Lockdown::Controller

